#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/i2c.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>

#include <linux/platform_device.h>

#include "i2c_oled.h"

/*------------------字符设备内容----------------------*/
#define DEV_NAME "i2c_oled"
#define DEV_CNT (1)

/*定义 oled 资源结构体，保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
static dev_t oled_devno;				 //定义字符设备的设备号
static struct cdev oled_chr_dev;		 //定义字符设备结构体chr_dev
struct class *class_oled;			 //保存创建的类
struct device *device_oled;			 // 保存创建的设备
struct device_node *oled_device_node; //oled的设备树节点结构体

/*------------------IIC设备内容----------------------*/
struct i2c_client *oled_client = NULL; //保存mpu6050设备对应的i2c_client结构体，匹配成功后由.prob函数带回。
u8 oled_init_data[] = {
	0xae, 0x20, 0x10, 0xb0, 0xc8,
	0x00, 0x10, 0x40, 0x81, 0xf,
	0xa1, 0xa6, 0xa8, 0x3F, 0xa4,
	0xd3, 0x00, 0xd5, 0xf0, 0xd9,
	0x22, 0xda, 0x12, 0xdb, 0x20,
	0x8d, 0x14, 0xaf};
/*
*函数功能：向oled发送一个命令
*i2c_client oled设备驱动对应的i2c_client结构体。
*command  要发送的命令。
*返回值：成功，返回0 失败返回负数。
*/
static int oled_send_command(struct i2c_client *oled_client, u8 command)
{
	int error = 0;
	u8 write_data[2];
	struct i2c_msg send_msg; //要发送的数据结构体

	/*设置要发送的数据*/
	write_data[0] = OLED_COMMEND_ADDR;  // 要写入的内部地址
	write_data[1] = command;

	/*发送 iic要写入的地址 reg*/
	send_msg.addr = oled_client->addr; //mpu6050在 iic 总线上的地址
	send_msg.flags = 0;					  //标记为发送数据
	send_msg.buf = write_data;			  //写入的首地址
	send_msg.len = 2;					  //reg长度

	/*执行发送*/
	error = i2c_transfer(oled_client->adapter, &send_msg, 1);
	if (error != 1)
	{
		printk(KERN_DEBUG "\n i2c_transfer command error \n");
		return -1;
	}
	return 0;
}


/*
*函数功能：向oled发送一组命令
*i2c_device oled设备驱动对应的i2c_client结构体。
*commands  要发送的数据。
*length 发送命令数量
*返回值：成功，返回0 失败返回负数。
*/
static int oled_send_commands(struct i2c_client *oled_client, u8 *commands, u16 length)
{
	int error = 0;
	int index ;
	
	for(index = 0; index < length; index++)
	{
		error = oled_send_command(oled_client, *(commands++));
		if(error < 0)
			return -1;
	}
	
	return 0;
}

static int oled_send_one_u8(struct i2c_client *oled_client, u8 data)
{
	int error = 0;
	u8 write_data[2];
	struct i2c_msg send_msg; //要发送的数据结构体

	/*设置要发送的数据*/
	write_data[0] = OLED_DATA_ADDR;  // 要写入的内部地址
	write_data[1] = data;

	/*发送 iic要写入的地址 reg*/
	send_msg.addr = oled_client->addr; //mpu6050在 iic 总线上的地址
	send_msg.flags = 0;					  //标记为发送数据
	send_msg.buf = write_data;			  //写入的首地址
	send_msg.len = 2;					  //reg长度

	/*执行发送*/
	error = i2c_transfer(oled_client->adapter, &send_msg, 1);
	if (error != 1)
	{
		printk(KERN_DEBUG "\n i2c_transfer data error \n");
		return -1;
	}
	return 0;
}

/*
*向 oled 发送数据
*oled_client，指定oled 设备驱动的i2c 结构体
*data, 要发送数据的地址
*length，发送的数据长度
*/
static int oled_send_data(struct i2c_client *oled_client, u8 *data, u16 length)
{
	int error = 0;
	int index ;
	
	for(index = 0; index < length; index++)
	{
		error = oled_send_one_u8(oled_client, *(data++));
		if(error < 0)
			return -1;
	}
	
	return 0;
}

/*
* 填充整个OLED 显示屏 
* bmp_dat， 填充的值 
*/
void oled_fill(unsigned char bmp_dat)
{
	u8 y, x;
	for (y = 0; y < 8; y++)
	{
		oled_send_command(oled_client, 0xb0 + y);
		oled_send_command(oled_client, 0x01);
		oled_send_command(oled_client, 0x10);
		for (x = 0; x < 128; x++)
		{
			oled_send_one_u8(oled_client, bmp_dat);
		}
	}
}

/*
*向 oled 发送要显示的数据，x, y 指定显示的起始位置，支持自动换行
*spi_device，指定oled 设备驱动的spi 结构体
*display_buffer, 数据地址
*x, y,起始坐标。
*length， 发送长度
*/
static int oled_display_buffer(u8 *display_buffer, u8 x, u8 y, u16 length)
{
	u16 index = 0;
	int error = 0;

	do
	{
		/*设置写入的起始坐标*/
		error += oled_send_command(oled_client, 0xb0 + y);
		error += oled_send_command(oled_client, ((x & 0xf0) >> 4) | 0x10);
		error += oled_send_command(oled_client, (x & 0x0f) | 0x01);

		if (length > (X_WIDTH - x))  // 长度长于起始位置到边缘位置的距离
		{
			error += oled_send_data(oled_client, display_buffer + index, X_WIDTH - x);
			length -= (X_WIDTH - x);
			index += (X_WIDTH - x);
			x = 0;
			y++;
		}
		else  // 剩下的长度在当前行就可以显示完全
		{
			error += oled_send_data(oled_client, display_buffer + index, length);
			index += length;
			// x += length;
			length = 0;
		}

	} while (length > 0);

	if (error != 0)
	{
		/*发送错误*/
		printk("oled_display_buffer error! %d \n",error);
		return -1;
	}
	return index;
}


/*oled 初始化函数*/
void spi_oled_init(void)
{
	/*初始化oled*/
	oled_send_commands(oled_client, oled_init_data, sizeof(oled_init_data));

	/*清屏*/
	oled_fill(0x00);
}

/*字符设备操作函数集，open函数实现*/
static int oled_open(struct inode *inode, struct file *filp)
{
	spi_oled_init(); //初始化显示屏
	return 0;
}


/*字符设备操作函数集，.write函数实现*/
static ssize_t oled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *off)
{
	int copy_number=0;

	/*申请内存*/
	oled_display_struct *write_data;
	write_data = (oled_display_struct*)kzalloc(cnt, GFP_KERNEL);

	copy_number = copy_from_user(write_data, buf,cnt);
	oled_display_buffer(write_data->display_buffer, write_data->x, write_data->y, write_data->length);

	/*释放内存*/
	kfree(write_data);
	return 0;
}

/*字符设备操作函数集，.release函数实现*/
static int oled_release(struct inode *inode, struct file *filp)
{
	oled_send_command(oled_client, 0xae);//关闭显示
	return 0;
}

/*字符设备操作函数集*/
static struct file_operations oled_chr_dev_fops = {
	.owner = THIS_MODULE,
	.open = oled_open,
	.write = oled_write,
	.release = oled_release};


/*----------------平台驱动函数集-----------------*/
static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id)
{

	int ret = -1; //保存错误状态码

	printk(KERN_EMERG "\t  match successed  \n");
	/*---------------------注册 字符设备部分-----------------*/

	//采用动态分配的方式，获取设备编号，次设备号为0，
	//设备名称为rgb-leds，可通过命令cat  /proc/devices查看
	//DEV_CNT为1，当前只申请一个设备编号
	ret = alloc_chrdev_region(&oled_devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0)
	{
		printk("fail to alloc oled_devno\n");
		goto alloc_err;
	}

	//关联字符设备结构体cdev与文件操作结构体file_operations
	oled_chr_dev.owner = THIS_MODULE;
	cdev_init(&oled_chr_dev, &oled_chr_dev_fops);

	// 添加设备至cdev_map散列表中
	ret = cdev_add(&oled_chr_dev, oled_devno, DEV_CNT);
	if (ret < 0)
	{
		printk("fail to add cdev\n");
		goto add_err;
	}

	/*创建类 */
	class_oled = class_create(THIS_MODULE, DEV_NAME);

	/*创建设备 DEV_NAME 指定设备名，*/
	device_oled = device_create(class_oled, NULL, oled_devno, NULL, DEV_NAME);
	oled_client = client;
	return 0;

add_err:
	// 添加设备失败时，需要注销设备号
	unregister_chrdev_region(oled_devno, DEV_CNT);
	printk("\n error! \n");
alloc_err:

	return -1;
}

static int oled_remove(struct i2c_client *client)
{
	/*删除设备*/
	device_destroy(class_oled, oled_devno);	  //清除设备
	class_destroy(class_oled);					  //清除类
	cdev_del(&oled_chr_dev);						  //清除设备号
	unregister_chrdev_region(oled_devno, DEV_CNT); //取消注册字符设备
	return 0;
}

/*定义ID 匹配表*/
static const struct i2c_device_id gtp_device_id[] = {
	{"fire,i2c_oled", 0},
	{}};

/*定义设备树匹配表*/
static const struct of_device_id oled_of_match_table[] = {
	{.compatible = "fire,i2c_oled"},
	{/* sentinel */}};


/*定义i2c总线设备结构体*/
struct i2c_driver oled_driver = {
	.probe = oled_probe,
	.remove = oled_remove,
	.id_table = gtp_device_id,
	.driver = {
		.name = "fire,i2c_oled",
		.owner = THIS_MODULE,
		.of_match_table = oled_of_match_table,
	},
};

/*
*驱动初始化函数
*/
static int __init oled_driver_init(void)
{
	int ret;
	pr_info("oled_driver_init\n");
	ret = i2c_add_driver(&oled_driver);
	return ret;
}

/*
*驱动注销函数
*/
static void __exit oled_driver_exit(void)
{
	pr_info("oled_driver_exit\n");
	i2c_del_driver(&oled_driver);
}

module_init(oled_driver_init);
module_exit(oled_driver_exit);

MODULE_LICENSE("GPL");
